use axum::{response::Response, BoxError};
use futures::future::BoxFuture;

use bytes::Bytes;
use http::{self, Request};
use http_body::Body as HttpBody;
use std::{
  boxed::Box,
  convert::Infallible,
  task::{Context, Poll},
};
use tower::{Layer, Service};

use axum_casbin::CasbinVals;

#[derive(Clone)]
struct AuthLayer;

impl<S> Layer<S> for AuthLayer {
  type Service = AuthMiddleware<S>;

  fn layer(&self, inner: S) -> Self::Service {
    AuthMiddleware { inner }
  }
}

#[derive(Clone)]
struct AuthMiddleware<S> {
  inner: S,
}

impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for AuthMiddleware<S>
where
  S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible> + Clone + Send + 'static,
  S::Future: Send + 'static,
  ReqBody: Send + 'static,
  Infallible: From<<S as Service<Request<ReqBody>>>::Error>,
  ResBody: HttpBody<Data = Bytes> + Send + 'static,
  ResBody::Error: Into<BoxError>,
{
  type Response = S::Response;
  type Error = S::Error;
  // `BoxFuture` is a type alias for `Pin<Box<dyn Future + Send + 'a>>`
  type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

  fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    self.inner.poll_ready(cx)
  }

  fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
    let not_ready_inner = self.inner.clone();
    let mut inner = std::mem::replace(&mut self.inner, not_ready_inner);

    Box::pin(async move {
      let vals = CasbinVals { subject: String::from("alice"), domain: None };
      req.extensions_mut().insert(vals);
      inner.call(req).await
    })
  }
}
